#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         KERFProcessor.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines functions for evaluating/replacing
#               Knowledge Engine Report Field (KERF) expressions with 
#               information in an assessment.
#
#               A KERF is a fragment of xml that specifies a python
#               expression, possibly made up of references to 
#               component and properties which when resolved and
#               evaluated produces a string.
#
#               Example 1: Simple KERF
#               ------------------------
#               <kerf>hello world</kerf>
#
#                   When evaluated returns the string 'hello world'
#
#               Example 2: KERF in other text
#               ------------------------
#               <b><kerf>hello world</kerf></b>
#
#                   When evaluated returns the string '<b>hello world</b>'
#
#               Example 3: KERF with an equation
#               ------------------------
#               <b><kerf>3 * 4</kerf></b>
#
#                   When evaluated returns the string '<b>12</b>'
#
#               Example 4: KERF with a component property reference
#               ------------------------
#               <i><kerf><$ txt1234:title $></kerf></i>
#
#                   When evaluated returns the string '<i>X</i>', 
#                   where X is the title property of the component
#                   with the identity 'txt1234'
#
#               Example 5: KERF with a property reference (componentless)
#               ------------------------
#               <i><kerf><$ value $></kerf></i>
#
#                   When evaluated returns the string '<i>X</i>', 
#                   where X is the value of property of the component
#                   passed into the resolveKERF function (the local component)
#
#               Example 6: KERF with a property reference in an equation
#               ------------------------
#               <i><kerf><$ value $> * 5.5</kerf></i>
#
#                   When evaluated returns the string '<i>X</i>', 
#                   where X is the value of property of the component
#                   passed into the resolveKERF function (the local component)
#                   multiplied by 5.3
#
# See Also:     Report
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *

#####################################################################
## Zope Library Imports
# (none)
 
#####################################################################
## Python Library Imports
# (none)

#####################################################################
            
def resolveKERFProperties(kerf, assessment, component=None):
    """
        PRIVATE: Resolves all of the <$ ... $> component property references
        in the specified kerf string using the provided assessment
        component provide references to 
        local objects for componentless property references

        RETURNS: a python expression that may be evaluated with eval
    """
    #component property tokens
    START_TOKEN = "<$"
    END_TOKEN = "$>"

    #the result thus far
    result = kerf

    #find the first property markup
    startIndex = result.find(START_TOKEN)

    #keep resolving properties until the kerf is resolved
    while startIndex >= 0:
        #get the endIndex
        endIndex = result.find(END_TOKEN, startIndex)

        #was an endIndex located
        if endIndex >= 0:
            #get the property to resolve
            property = result[startIndex + len(START_TOKEN):endIndex]

            #find the location of the identity:property separator
            propertyIndex = property.find(":")

            #is a <$ component:property $> property provided?
            if propertyIndex >= 0:
                #yes... 

                #get component identity
                identity = property[:propertyIndex].strip()

                #get the component using the assessment model
                component = assessment.getModel().getComponent(identity)                

                #get component property name
                name = property[propertyIndex + 1:].strip()

            else:                
                #no, so assume in the format <$ property $>

                #get component identity of the component (using the provided component)
                identity = component.getIdentity()

                #get component property name
                name = property.strip()

            #is the component defined?
            if component != None:

                #fetch the value of the property from the component using the provided assessment
                value = component.getComponentProperty(identity, name, assessment)

                #was the property found?
                if value == None:
                    value = "<font color=red>Unknown Property <b>" + name + "</b> for Component <b>" + identity + "</b></font>"    

            else:
                #unknown component
                value = "<font color=red>Unknown Component <b>" + identity + "</b> in Model</font>"

            #replace resolved property into kerf
            result = result[:startIndex] + repr(value) + result[endIndex+len(END_TOKEN):]

            #find the next property markup
            startIndex = result.find(START_TOKEN)

        else:
            startIndex = -1

    #return the result
    return result

#---------------------------------------------------------------#

def resolveKERF(content, assessment, component=None):
    """
        PUBLIC: Resolves the <kerf>...</kerf> (Knowledge Engine Report Fields)
        markups in the specified content (a string) given an
        assessment.
    """

    #start and end tokens
    START_TOKEN = "<kerf>"
    END_TOKEN = "</kerf>"

    #the result thus far
    result = content

    #find the first <kerf> markup
    startIndex = result.find(START_TOKEN)

    #keep resolving kerfs until the content is resolved
    while startIndex >= 0:
        #get the endIndex
        endIndex = result.find(END_TOKEN, startIndex)

        #was an endIndex located
        if endIndex >= 0:
            #get the kerf to resolve
            kerf = result[startIndex + len(START_TOKEN):endIndex]

            #resolve kerf properties
            kerf = resolveKERFProperties(kerf, assessment, component)

            #try to evaluate the kerf expression (which is a python expression)
            try:
                value = eval(kerf)
            except:
                value = "<font color=red>Could not evaluate <b>%s</b> in component <b>%s</b></font>" % (kerf,component.getIdentity())

            #replace resolved kerf into content
            result = "%s%s%s" % (result[:startIndex], value, result[endIndex+len(END_TOKEN):])

            #find the next <kerf> markup
            startIndex = result.find(START_TOKEN)

        else:
            startIndex = -1

    #return the result
    return result

#---------------------------------------------------------------#

def updateKERFIdentities(kerf, identityMappings):
    """
        PRIVATE: Resolves the component identities for the specified 
        kerf <$ ... $>

        RETURNS: returns the kerf with old component identities
        replace with those as specified in the identityMappings
    """

    #component property tokens
    START_TOKEN = "<$"
    END_TOKEN = "$>"

    #the result thus far
    result = kerf

    #find the first property markup
    startIndex = result.find(START_TOKEN)

    #keep updating identities until the kerf is resolved
    while startIndex >= 0:
        #get the endIndex
        endIndex = result.find(END_TOKEN, startIndex)

        #was an endIndex located
        if endIndex >= 0:
            #get the property to resolve
            property = result[startIndex + len(START_TOKEN):endIndex]

            #find the location of the identity:property separator
            propertyIndex = property.find(":")

            #is a <$ component:property $> property provided?
            if propertyIndex >= 0:
                #yes... 

                #get component identity
                identity = property[:propertyIndex].strip()

                #is the identity in the identityMappings?                    
                if identityMappings.has_key(identity):
                    #get the new identity
                    identity = identityMappings[identity]

                #get component property name
                name = property[propertyIndex + 1:].strip()

                #replace resolved property into kerf
                result = result[:startIndex] + START_TOKEN + " " + identity + ":" + name + " " + END_TOKEN + result[endIndex+len(END_TOKEN):]

            else:                
                #no, so assume in the format <$ property $>

                #get component property name
                name = property.strip()

                #replace resolved property into kerf
                result = result[:startIndex] + START_TOKEN + " " + name + " " + END_TOKEN + result[endIndex+len(END_TOKEN):]

            #find the next property markup
            startIndex = result.find(START_TOKEN, startIndex + 1)

        else:
            startIndex = -1

    #return the result
    return result

#---------------------------------------------------------------#

def updateKERF(content, identityMappings):
    """
        PUBLIC: Updates the component identities in <kerf>...</kerf> 
        (Knowledge Engine Report Fields) markups in the specified 
        content (a string) given a mapping from old to new component identities.
    """

    #start and end tokens
    START_TOKEN = "<kerf>"
    END_TOKEN = "</kerf>"

    ###print "DEBUG: Report.updateKERF: Before [%s] with [%s]" % (content, repr(identityMappings))

    #the result thus far
    result = content

    #find the first <kerf> markup
    startIndex = result.find(START_TOKEN)

    #keep resolving kerfs until the content is resolved
    while startIndex >= 0:
        #get the endIndex
        endIndex = result.find(END_TOKEN, startIndex)

        #was an endIndex located
        if endIndex >= 0:
            #get the kerf to resolve
            kerf = result[startIndex + len(START_TOKEN):endIndex]

            ###print "DEBUG: Report.updateKERF: Before %s" % (kerf,)

            #resolve kerf properties
            kerf = updateKERFIdentities(kerf, identityMappings)

            ###print "DEBUG: Report.updateKERF: After %s" % (kerf,)

            #replace resolved kerf into content
            result = result[:startIndex] + START_TOKEN + kerf + END_TOKEN + result[endIndex+len(END_TOKEN):]

            #find the next <kerf> markup
            startIndex = result.find(START_TOKEN, startIndex + 1)

        else:
            startIndex = -1

    ###print "DEBUG: Report.updateKERF: After [%s] with [%s]" % (content, repr(identityMappings))

    #return the result
    return result

#---------------------------------------------------------------#

#####################################################################
